package com.cskaoyan.javase.object._8callback;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 将回调方法作为参数传入另外一个方法，实际开发中有什么意义呢？
 * 比较典型的，将回调方法传入另外一个方法后，回调方法用于表示一种规则，比如：
 *      1.计算的规则（比如上面的小明案例）
 *      2.过滤规则
 *      3.映射规则
 *      4.排序规则
 *      5.规约规则
 *      ....
 *
 * 这里我们演示一下，使用"方法的回调"实现过滤规则，增加方法调用的灵活性。
 * 需求是：过滤数组容器，要求能够实现任意规则过滤任意数组。
 * 过滤数组，说白了就是删除数组中的元素，如果是一个固定规则的过滤很简单，但任意规则呢？这就需要使用"方法回调"。
 *
 * @since 22:01
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Test {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("123");
        list.add("abc");
        list.add("ddddd");
        List<String> collect = list.stream().filter(o -> o.length() > 3).collect(Collectors.toList());
        // 测试过滤方法
        Integer[] arr = {1, -2, 3, -4, 5};
        // 需求过滤以上数组中的小于0的数,留下大于0的
        Object[] result = filterObjectArrByIFilter(arr, o -> ((Integer) o) > 0);
        for (Object ele : result) {
            System.out.println(((Integer) ele));
        }
        System.out.println("----------------------------");
        Student[] arr2 = new Student[5];
        arr2[0] = new Student(18, 100);
        arr2[1] = new Student(16, 80);
        arr2[2] = new Student(20, 90);
        arr2[3] = new Student(30, 77);
        arr2[4] = new Student(12, 88);
        // 只留下成年的学生
        Object[] result2 = filterObjectArrByIFilter(arr2, o -> ((Student) o).getAge() >= 18);
        for (Object ele : result2) {
            System.out.println(((Student) ele));
        }

        System.out.println("----------------------------------------");
        // 直接将一个Student数组,映射成所有学生的年龄数组
        Object[] result3 = mapObjectArrByIMap(arr2, o -> ((Student) o).getAge());
        for (Object ele : result3) {
            System.out.println(((Integer) ele));
        }

        System.out.println("**********************************");
        // 先过滤Student数组,留下未成年的学生,然后映射成他们的成绩数组
        // 以上代码实现,可以参考Java Stream API的写法
        Object[] result4 = MyStream.toStream(arr2).filter(o -> ((Student) o).getAge() < 18).map(o -> ((Student) o).getScore()).toArr();
        for (Object ele : result4) {
            System.out.println(((Double) ele));
        }

    }

    /**
     * 该方法根据传入的映射规则,将目标数组映射成对应数组,然后返回
     * @param target 被映射的目标数组
     * @param im 映射的规则
     * @return java.lang.Object[]
     * @author wuguidong@cskaoyan.onaliyun.com
     * @since 2022/10/11 22:29
     */
    public static Object[] mapObjectArrByIMap(Object[] target, IMap im) {
        /*
            1.创建新数组,新数组的长度和老数组长度是一样的
            2.根据映射的规则,给新数组赋值
         */
        Object[] result = new Object[target.length];
        int newIndex = 0;
        for (Object ele : target) {
            // 把老数组中的元素,通过映射的规则,得到一个新元素,这个新元素就是新数组中的元素
            result[newIndex] = im.map(ele);
            newIndex++;
        }
        return result;
    }

    /*
        在Test类当中,定义一个方法,用来实现对任何数组的,任意规则过滤
        该方法的返回值类型是什么呢?
        过滤操作是不可能在原先数组中完成的,所以需要新建一个数组,那么新建的数组肯定需要返回出来
        返回值类型要写Object[]
     */

    /**
     * 根据传入的过滤的规则不同,过滤数组中的元素,返回新数组
     *
     * @param target 被过滤元素的目标数组
     * @param it 过滤的规则
     * @return java.lang.Object[]
     * @author wuguidong@cskaoyan.onaliyun.com
     * @since 2022/10/11 22:10
     */
    public static Object[] filterObjectArrByIFilter(Object[] target, IFilter it) {
        /*
            具体如何实现呢?
            1.需要计算出新数组的长度
            2.遍历老数组,给新数组赋值
         */
        // 1.需要计算出新数组的长度,新数组的长度 = 老数组长度 - 被过滤的元素的个数/新数组的长度 =留下的元素的个数
        // 统计一下被过滤元素的个数或者也可以直接统计留下来的元素的个数
        int count = 0;
        for (Object ele : target) {
            // filter方法返回true表示留下,false表示被过滤掉了
            if (it.filter(ele)) {
                count++;
            }
        }
        // count此时就是留下的元素的个数,也就是新数组的长度
        Object[] result = new Object[count];

        // 2.遍历老数组,给新数组赋值
        int newIndex = 0;
        for (Object ele : target) {
            // filter方法返回true表示留下,false表示被过滤掉了
            if (it.filter(ele)) {
                result[newIndex] = ele;
                newIndex++;
            }
        }
        return result;
    }
}

// 自定义自己的Stream类
class MyStream {

    // 将要处理的Object数组,作为自身的静态成员变量
    private static Object[] target;

    // 要想使用该类进行流式编程,需要首先给target赋值
    public static MyStream toStream(Object[] o) {
        target = o;
        return new MyStream();
    }

    public Object[] toArr() {
        return target;
    }

    /**
     * 该方法根据传入的映射规则,将目标数组映射成对应数组,然后返回
     * @param im 映射的规则
     * @return java.lang.Object[]
     * @author wuguidong@cskaoyan.onaliyun.com
     * @since 2022/10/11 22:29
     */
    public MyStream map(IMap im) {
        Object[] result = new Object[target.length];
        int newIndex = 0;
        for (Object ele : target) {
            result[newIndex] = im.map(ele);
            newIndex++;
        }
        // 修改以后的result应该赋值给target,target是一个静态成员,类的全局唯一
        target = result;
        return this;
    }

    /**
     * 根据传入的过滤的规则不同,过滤数组中的元素,返回新数组
     *
     * @param it 过滤的规则
     * @return java.lang.Object[]
     * @author wuguidong@cskaoyan.onaliyun.com
     * @since 2022/10/11 22:10
     */
    public MyStream filter(IFilter it) {
        int count = 0;
        for (Object ele : target) {
            if (it.filter(ele)) {
                count++;
            }
        }
        Object[] result = new Object[count];
        int newIndex = 0;
        for (Object ele : target) {
            if (it.filter(ele)) {
                result[newIndex] = ele;
                newIndex++;
            }
        }
        target = result;
        return this;
    }
}

// 首先利用回调方法,将过滤的规则传入另一个方法,所以需要一个功能接口
@FunctionalInterface
interface IFilter {
    /*
        抽象方法如何写呢?
        这个过滤的规则表示对任何(Object)数组中的元素的过滤规则,所以过滤的是Object数组
        在过滤的时候,一个元素要么留下,要么被丢弃,只有两种可能性
        那么这个表示过滤规则的方法,返回值类型是什么?
        要写布尔类型,这里不妨规定一下:
            过滤规则的方法返回true表示留下,false表示被过滤掉了
        方法既然表示对Object容器的过滤,那么过滤的就是Object对象
        所以方法的形参数据类型需要写Object
     */
    boolean filter(Object o);
}

@FunctionalInterface
interface IMap {
    /*
        映射是将一列数据,变成对应的另一列数据
        这个映射接口中的抽象方法就表示一种映射的规则
        将某一个Object数组,映射成另一个Object数组
        对应映射的规则就是:
        将Object数组中的某一个Object元素,映射成另外一个Object元素
     */
    Object map(Object o);
}

class Student {
    private int age;
    private double score;

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    public int getAge() {
        return age;
    }

    public double getScore() {
        return score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", score=" + score +
                '}';
    }
}